Coming from the Python ecosystem, the range of packages available in Julia can seem somewhat limited. This is offset, however, by the ease of calling out to packages written in other languages from within Julia.
In particular, Python interoperability is very easy, thanks to the PyCall
package. This is loaded with
In [1]:
using PyCall
PyCall
has a high-level interface that is designed so that the "transport" between Julia and Python is transparent from the user's point of view. For example, to import the Python math
module, we do
In [2]:
@pyimport math
In [3]:
macroexpand(:(@pyimport math))
Out[3]:
We can now mix and match Python calls, labelled by the math.
qualifier, and Julia calls:
In [3]:
math.sin(0.3*math.pi) - sin(0.3*pi)
Out[3]:
Array objects are automatically converted:
In [9]:
@pyimport numpy.random as nprandom
nprandom.rand(3,4)
Out[9]:
Let's define a Julia function:
In [4]:
objective = x -> cos(x) - x
Out[4]:
This is the Julia syntax for an anonymous function (like lambda
in Python).
In [5]:
objective(3)
Out[5]:
We can pass this Julia function to a Python module:
In [6]:
?so.newton
In [7]:
@pyimport scipy.optimize as so
so.newton(objective, 1)
Out[7]:
The main difference from Python is how to access member elements of Python structures.
Julia has ODE solvers in the ODE.jl
and Sundials.jl
packages. But we can also call Python solvers:
In [11]:
@pyimport scipy.integrate as integrate
In [12]:
f(x,t) = -x
Out[12]:
In [13]:
t = [0:0.1:10];
In [14]:
soln = integrate.odeint(f, 1, t);
In [9]:
using PyPlot
Note that the PyPlot
module provides a higher-level wrapper than PyCall
around matplotlib
.
In [15]:
plot(t, soln)
plot(t, exp(-t))
Out[15]:
Accessing fields (properties) and methods of Python objects uses the obj.a
and obj.b()
syntax, where obj
is a Python object.
However, currently the obj.b
syntax in Julia is restricted to accessing fields of Julia composite types.
For this reason, to access fields and methods of Python objects via PyCall, it is necessary to use the syntax
obj[:a]
for fields, and
obj[:a]()
for methods
Here, we are using the Julia syntax :a
to mean the Julia symbol a
.
The high-level PyCall
interface is built on top of a lower-level interface which deals with the "transport" of objects between Python and Julia, based on a PyObject
Julia type that wraps PyObject*
in C, and represents a reference to a Python object.
In [16]:
PyObject(3)
Out[16]:
In [17]:
x = rand(5, 5)
Out[17]:
In [18]:
xx = PyObject(x)
Out[18]:
In [19]:
typeof(xx)
Out[19]:
In [20]:
names(xx)
Out[20]:
In [21]:
xx.o
Out[21]:
In [11]:
# xx.shape in Python becomes:
xx[:shape]
Out[11]:
In [14]:
typeof(ans) # the result has already been translated back into a Julia object
Out[14]:
Julia arrays are passed into Python without a copy. By default the resulting Python array is copied when a result is requested in Julia; this can be avoided at a lower level using pycall
and PyArray
.
Exercise: Use your favourite Python package from Julia!
Julia has a simple way to call C and Fortran functions in shared libraries, via the ccall
function.
In [25]:
help("ccall")
We see that we must specify:
the return type of the function
the argument types that the function accepts, as a tuple
and the arguments themselves
A simple example is to call the clock function:
In [4]:
t = ccall( (:clock, "libc"), Int32, ())
Out[4]:
In [10]:
t
Out[10]:
In [11]:
typeof(t)
Out[11]:
In [36]:
help("ccall")
In [ ]:
Clong
In [12]:
path = ccall( (:getenv, "libc"), Ptr{Uint8}, (Ptr{Uint8},), "PATH")
Out[12]:
Here, Ptr
denotes a pointer to the given type.
In [31]:
path
Out[31]:
In [13]:
bytestring(path)
Out[13]:
In [34]:
?bytestring
In [37]:
Ptr
Out[37]:
In [38]:
methods(Ptr)
Out[38]:
In [39]:
methodswith(Ptr)
Out[39]:
In [1]:
type F
f::Function
x::Float64
end
In [2]:
hello(x) = x^2
Out[2]:
In [6]:
myfunc = F(hello, 3.5)
Out[6]:
In [4]:
myfunc
In [27]:
myfunc.f
Out[27]:
In [8]:
myfunc.f(myfunc.x)
Out[8]:
In [ ]: